module CodeGraphs

  require 'parse_tree'
  
  class CodeDependencies
    attr_accessor :graph
    
    def initialize directory = nil
      @directory = directory || File.dirname(__FILE__)+'/../sandbox'
      @classes = {}
      @graph = Graph.new
    end

    
    def inheritance      
      files do |line|
        if m = (line.match /class (([A-Z][A-Za-z]+:?:?)+) ?<? ?(([A-Z][A-Za-z]+:?:?)+)?/)
          #p [m[1], m[3]]
          if m[3] != nil
            graph.join node(m[1]), node(m[3])
          else
            graph.join node(m[1]), node("Class")
          end                    
        end
      end
      @graph.dump
    end
    
    
    def method_usage
      current_module = nil
      current_class = nil
      banned_methods = []
      [Kernel, Object, String.new(""), [], {}, File].each { |c| 
        banned_methods.push(*c.public_methods.collect { |m| m.to_s})
      }
      
      files do |line|
        if m = line.match(/module (([A-Z][A-Za-z]+:?:?)+)/)
          current_module = m[1]
          next
        end
        if match = line.match(/class (([A-Z][A-Za-z]+:?:?)+) ?<? ?(([A-Z][A-Za-z]+:?:?)+)?/)
          current_class_node = node(match[1])
          begin
            if current_module
              current_class = Magic.to_const(current_module + '::' + match[1])
            else
              current_class = Magic.to_const(match[1])
            end
            my_methods = (current_class.public_methods + current_class.instance_methods)
            traverse(ParseTree.translate(current_class)) do |a|
              if a[0] == :call && a[2].is_a?(Symbol)
                m = a[2]
              elsif a[0] == :call && a[1].is_a?(Symbol)
                m = a[1]
              elsif a[0] == :fcall && a[1].is_a?(Symbol)
                m = a[1]
              elsif a[0] == :vcall && a[1].is_a?(Symbol)
                m = a[1]
              else
                next
              end
              m = m.to_s
              if banned_methods.include?(m) || my_methods.include?(m)
                next
              end
              next if m[0,1] == '@'
              next if m[0,1] == '_'
              next if m[0,1] == '='
              next if m[0,1] =~ /[A-Z]/
              next if m[0,1] !~ /^[a-z]/
              next if m == ''
              if m =~ /=$/
                m = m.match(/([^=]+)=$/)[1]
              end
              graph.join current_class_node, node(m)
            end
          rescue NameError
            if current_module
              current_module = nil
              retry
            else
              #puts $!
              #puts $!.backtrace
            end
          end

        end
      end
      @graph.dump      
    end
 
    def traverse array, &block
      array.each do |item|
        if item.is_a? Array
          yield item
          traverse item, &block
        end
      end
    end
    
    def instantiation      
      current = nil
      long_comment = false
      files do |line|
        
        if line.match("^=end")
          long_comment = false
        end

        if line.match("^=begin")
          long_comment = true
        end
        
        next if long_comment

        next if line.match("#")

        #class definition
        if m = (line.match /class (([A-Z][A-Za-z]+:?:?)+)/)
          current = node m[1]
          next
        end

        # class used in code
        if m = (line.match /(([A-Z][A-Za-z]+:?:?)+).new/)
          node = node m[1]
          if current
            @graph.join current, node
          else
            #assign_to_current_class << node
            #warn "no current class for #{m[1]}"
          end            
        end
        
      end
      @graph.dump        
    end
    
    private
    
        
    def files
      seeker = Loader::FileSeeker.new
      seeker.blacklist.add(/test/)
      seeker.folder_blacklist.add(/test/)
      seeker.folder_blacklist.add(/doc/)
      seeker.folder_blacklist.add(/nbproject/)
      sources = seeker.files_under_dir(@directory, /\.rb/)
      
      sources.each_pair do |filename, file|
        File.open(file) { |f|
          while line = f.gets
            yield line
          end
        }
      end
    end

    def node class_name
      graph.node name(class_name)
    end
    
    def name class_name
      class_name.to_s.split('::').last
    end
    
  end
end